package org.zhaya.sdk.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Enums;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.zhaya.sdk.constant.OperatorEnum;
import org.zhaya.sdk.model.*;
import org.zhaya.sdk.processors.condition.ProcessorEnum;
import org.zhaya.sdk.swamill.ProcessDoc;
import org.zhaya.sdk.util.ClonerUtils;
import org.zhaya.sdk.util.PipelineUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * @Author Ryan Yang
 * @Date 2022/10/10 10:43 上午
 */
@Slf4j
public class PipelineDebugService {

    /**
     * 模型调试
     *
     * @param modelJson   模型json
     * @param debugNodeId 调试节点id
     * @param debugData   调试数据（单条）
     * @return 调试结果
     */
    public static List<PipelineDedugResult> debug(String modelJson, String debugNodeId, String debugData) {
        List<PipelineNode> pipelineNodeList = PipelineParseService.parse(modelJson);
        List<ProcessDoc> docs = PipelineUtils.createDocsForMessage(debugData);
        return debug(pipelineNodeList, debugNodeId, docs);
    }

    public static List<PipelineDedugResult> debug(List<PipelineNode> pipelineNodeList, String debugNodeId, List<ProcessDoc> docs) {
        //根据调试节点生成可达路径
        List<TestDataConfig> configs = convertToNodeIdConfig(pipelineNodeList, debugNodeId);
        //依次执行每一条可达路径
        List<PipelineDedugResult> result = Lists.newArrayListWithExpectedSize(configs.size());
        for (TestDataConfig config : configs) {
            List<PipelineExecuteResult> execOnceResults = PipelineExecuteService.executeOnce(config.getConfig(), ClonerUtils.deepClone(docs));
            for (PipelineExecuteResult rs : execOnceResults) {
                result.add(PipelineDedugResult.builder().nodeId(config.getNodeId()).result(rs).build());
            }
        }
        //非流程汇聚场景
        if (result.size() <= 2) {
            return result;
        }
        //过滤执行结果为空的数据
        return result.stream().filter(item -> Objects.nonNull(item.getResult().getData())).collect(toList());
    }

    private static List<TestDataConfig> convertToNodeIdConfig(List<PipelineNode> allNodes, String debugNodeId) {
        if (CollUtil.isEmpty(allNodes) || StrUtil.isBlank(debugNodeId)) {
            return Collections.emptyList();
        }
        Map<String, PipelineNode> allNodeMap = allNodes.stream().collect(Collectors.toMap(PipelineNode::getNodeId, v -> v, (a, b) -> a));
        //获取调试节点的父节点（非流程拆分节点）
        List<String> testNodeIds = Lists.newArrayList();
        getParentNodeIds(allNodeMap.get(debugNodeId), allNodeMap, testNodeIds);
        testNodeIds.add(debugNodeId);
        List<TestDataConfig> configs = Lists.newArrayList();
        testNodeIds.stream().filter(allNodeMap::containsKey).forEach(testNodeIdItem -> configs.addAll(getTestDataConfigs(testNodeIdItem, allNodeMap)));
        return configs;
    }

    /**
     * 获取节点的上一个节点（非流程拆分节点）
     */
    private static void getParentNodeIds(PipelineNode debugNode, Map<String, PipelineNode> allNodeMap, List<String> debugNodeIds) {
        if (Objects.isNull(debugNode) || CollUtil.isEmpty(debugNode.getParentNodeIds())) {
            return;
        }
        for (String parentNodeId : debugNode.getParentNodeIds()) {
            PipelineNode parentNode = allNodeMap.get(parentNodeId);
            if (Objects.nonNull(parentNode) && !ProcessorManager.checkIsIf(parentNode.getType())
                    && !ProcessorManager.checkIsSourceOrSink(parentNode.getType())) {
                debugNodeIds.add(parentNodeId);
            } else {
                getParentNodeIds(parentNode, allNodeMap, debugNodeIds);
            }
        }
    }

    /**
     * 根据调试节点获取可达链路
     */
    private static List<TestDataConfig> getTestDataConfigs(String testNodeId, Map<String, PipelineNode> allNodeMap) {
        PipelineNode testNode = allNodeMap.get(testNodeId);
        //为调试节点填充DebugNode
        List<PipelineNode> pipeNodeTree = initPipeNodeTree(testNode);

        List<TestDataConfig> testConfigList = Lists.newArrayList();
        List<List<PipelineNode>> pipeNodeTreeList = Lists.newArrayList();
        getPipeNodeTreeList(testNode, allNodeMap, pipeNodeTree, pipeNodeTreeList);
        pipeNodeTreeList.stream().filter(CollUtil::isNotEmpty).forEach(pipeNodeTreeItem -> {
            Collections.reverse(pipeNodeTreeItem);
            for (int i = 0; i < pipeNodeTreeItem.size(); i++) {
                if (ProcessorManager.checkIsDebug(pipeNodeTreeItem.get(i).getType())) {
                    PipelineNode pipeNode = pipeNodeTreeItem.get(i);
                    pipeNodeTreeItem.remove(i);
                    pipeNodeTreeItem.add(pipeNode);
                }
            }
            testConfigList.add(new TestDataConfig(testNodeId, PipelineParseService.convertToConfig(pipeNodeTreeItem)));
        });
        return testConfigList;
    }

    /**
     * 填充DebugNode
     */
    private static List<PipelineNode> initPipeNodeTree(PipelineNode currentNode) {
        List<PipelineNode> pipeNodeGroup = Lists.newArrayListWithExpectedSize(2);
        pipeNodeGroup.add(currentNode);
        if (!ProcessorManager.checkIsDebug(currentNode.getType())) {
            PipelineNode customDebugNode = new PipelineNode();
            customDebugNode.setNodeId(IdUtil.fastUUID());
            customDebugNode.setName(ProcessorManager.DEBUG_PROCESSOR_TYPE);
            customDebugNode.setType(ProcessorManager.DEBUG_PROCESSOR_TYPE);
            customDebugNode.setConfig(StrPool.EMPTY_JSON);
            customDebugNode.setParentNodeIds(Collections.singletonList(currentNode.getNodeId()));
            pipeNodeGroup.add(customDebugNode);
        }
        return pipeNodeGroup;
    }

    /**
     * 递归获取节点树
     */
    private static void getPipeNodeTreeList(PipelineNode currentNode, Map<String, PipelineNode> allNodeMap, List<PipelineNode> pipeNodeTree, List<List<PipelineNode>> pipeNodeTreeList) {
        if (Objects.isNull(currentNode)) {
            return;
        }
        if (CollUtil.isEmpty(currentNode.getParentNodeIds()) && !ProcessorManager.checkIsSourceOrSink(currentNode.getType())) {
            if (!ProcessorManager.checkIsIf(currentNode.getType())) {
                pipeNodeTreeList.add(pipeNodeTree);
            }
            return;
        }
        for (String parentNodeId : currentNode.getParentNodeIds()) {
            PipelineNode currentNodeReplica = ObjectUtil.cloneByStream(currentNode);
            List<PipelineNode> pipeNodeTreeReplica = ObjectUtil.cloneByStream(pipeNodeTree);
            while (CollUtil.isNotEmpty(currentNodeReplica.getParentNodeIds())) {
                PipelineNode parentNodeReplica = ObjectUtil.cloneByStream(allNodeMap.get(parentNodeId));
                if (CollUtil.isNotEmpty(parentNodeReplica.getParentNodeIds()) && parentNodeReplica.getParentNodeIds().size() > 1) {
                    if (ProcessorManager.checkIsIf(parentNodeReplica.getType())) {
                        List<PipelineNode> tempNodes = ObjectUtil.cloneByStream(pipeNodeTreeReplica);
                        Collections.reverse(tempNodes);
                        executeOneTransferIfNode(parentNodeReplica, currentNodeReplica.getNodeId(), tempNodes);
                        pipeNodeTreeReplica.clear();
                    }
                    pipeNodeTreeReplica.add(parentNodeReplica);
                    getPipeNodeTreeList(parentNodeReplica, allNodeMap, pipeNodeTreeReplica, pipeNodeTreeList);
                    break;
                }
                if (!ProcessorManager.checkIsSourceOrSink(parentNodeReplica.getType())) {
                    if (ProcessorManager.checkIsIf(parentNodeReplica.getType())) {
                        List<PipelineNode> tempNodes = ObjectUtil.cloneByStream(pipeNodeTreeReplica);
                        Collections.reverse(tempNodes);
                        executeOneTransferIfNode(parentNodeReplica, currentNodeReplica.getNodeId(), tempNodes);
                        pipeNodeTreeReplica.clear();
                    }
                    pipeNodeTreeReplica.add(parentNodeReplica);
                } else {
                    pipeNodeTreeList.add(pipeNodeTreeReplica);
                    break;
                }
                if (CollUtil.isEmpty(parentNodeReplica.getParentNodeIds())) {
                    pipeNodeTreeList.add(pipeNodeTreeReplica);
                    break;
                }
                currentNodeReplica = parentNodeReplica;
                parentNodeId = currentNodeReplica.getParentNodeIds().get(0);
            }
        }
    }

    private static void executeOneTransferIfNode(PipelineNode ifNode, String currentNodeId, List<PipelineNode> beforeNodes) {
        List<Condition> conditionListByConfig = getConditionListByConfig(ifNode.getConfig());
        for (Condition next : conditionListByConfig) {
            String nodeId = next.getNodeId();
            if (currentNodeId.equals(nodeId)) {
                next.setThen(beforeNodes);
            }
        }
        ifNode.setConditions(conditionListByConfig);
    }

    public static List<Condition> getConditionListByConfig(String config) {
        List<Condition> result = Lists.newArrayList();
        Map parse = JSONUtil.toBean(config, Map.class);
        List<Map<String, Object>> conditions = (List<Map<String, Object>>) parse.get(ProcessorEnum.conditions.name());
        for (Map<String, Object> map : conditions) {
            Condition condition = new Condition();
            condition.setNodeId(map.get("nodeId").toString());
            String logic = map.get("logic").toString();
            List<Map<String, Object>> sawmillCondition = (List<Map<String, Object>>) map.get(ProcessorEnum.condition.name());
            List<String> strings = Lists.newArrayList();
            for (Map<String, Object> sawmillConMap : sawmillCondition) {
                Object operator = sawmillConMap.get(ProcessorEnum.operator.name());
                Object field = Objects.requireNonNull(sawmillConMap.get(ProcessorEnum.field.name()), "Some fields in the component condition are missing");
                Object value = sawmillConMap.get(ProcessorEnum.value.name());
                Map<String, Object> transfer = Enums.getIfPresent(OperatorEnum.class, operator.toString())
                        .toJavaUtil().orElseThrow(() -> new IllegalArgumentException("Illegal operator param !"))
                        .transfer(field.toString(), value);
                strings.add(JSONUtil.toJsonStr(transfer));
            }
            condition.setCondition(ImmutableMap.of(logic, strings));
            result.add(condition);
        }
        return result;
    }
}
